Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

28장. 시간 다루기

시간은 프로그램 어디에나 등장한다. 로그 타임스탬프, 만료 검사, 타임아웃, 주기적인 작업, 기록 정렬…

Go 는 time 패키지 하나로 대부분의 시간 작업을 처리한다. 이번 장에서 그 사용법을 둘러본다.

목표:

  • time.Time 으로 시각을 표현하기
  • 현재 시각을 얻고 임의의 시각을 만들기
  • 포맷팅과 파싱 (Go 의 독특한 reference time 이해)
  • 시간 사이의 차이와 연산
  • 타이머/Ticker 와 동시성 조합
  • 시간대(timezone) 다루기

28.1 time.Time

time.Time 은 “시간 위의 한 점” 을 나타내는 타입이다. 일상 언어로 말하면 “어떤 순간” 이다.

예를 들어 “2026년 1월 1일 오전 9시 0분 0초” 같은 값이 하나의 time.Time 이 된다.

내부에는 두 가지가 함께 들어 있다.

  • 그 순간 자체 (몇 년 몇 월 며칠 몇 시…)
  • 어떤 시간대(time zone) 기준인지

같은 순간이라도 시간대가 다르면 표시는 달라진다.

순간한국(KST)UTC
같은 순간09:0000:00

time.Time 은 이 모든 정보를 함께 들고 있다.

비교나 빼기 같은 연산은 시간대와 무관하게 “같은 순간인지” 를 기준으로 동작한다.


28.2 현재 시각 / 만들기

현재 시각

now := time.Now()
fmt.Println(now)
// 2026-05-24 13:00:00.123 +0900 KST

time.Now() 는 시스템 시계의 현재 시각을 반환한다. 보통 가장 자주 쓰는 함수다.

임의 시각 만들기

time.Date 로 직접 만들 수 있다.

t := time.Date(
    2026, time.May, 24,
    13, 0, 0, 0,
    time.Local,
)

인자는 순서대로 다음을 뜻한다.

인자의미
year
month월 (time.Month 상수)
day
hour시 (0~23)
min
sec
nsec나노초
loc시간대 (*time.Location)

월은 time.January 부터 time.December 까지의 상수를 쓴다. 1 같은 정수를 그대로 써도 동작한다 (time.Month(1)).


28.3 포맷팅과 파싱

Go 의 독특한 reference time

다른 언어는 보통 YYYY-MM-DD HH:mm:ss 같은 형식 문자열을 쓴다. Go 는 좀 다르다. 기준 시각 자체를 형식 문자열로 쓴다.

기준 시각은 외워야 한다.

2006-01-02 15:04:05 -0700 MST

각 자리는 이런 의미다.

부분의미
2006
01
02
15시 (24시간)
04
05
-0700시간대 오프셋
MST시간대 이름

순서대로 외우면 1 2 3 4 5 6 -7. “2006년 1월 2일 3시 4분 5초, -7 오프셋” 이라는 식으로 기억하면 좋다.

Format

t := time.Now()
t.Format("2006-01-02")
// "2026-05-24"
t.Format("2006/01/02 15:04")
// "2026/05/24 13:00"
t.Format("2006-01-02T15:04:05Z07:00")
// RFC3339 형식

기준 시각의 자리에 원하는 자리 표시자를 끼워 넣는 식이다.

Parse

문자열을 time.Time 으로 되돌리는 함수다.

t, err := time.Parse(
    "2006-01-02",
    "2026-05-24",
)

첫 번째 인자가 형식, 두 번째가 실제 값.

자주 쓰는 포맷 상수

표준 라이브러리가 자주 쓰는 형식을 상수로 제공한다.

time.RFC3339      // "2006-01-02T15:04:05Z07:00"
time.RFC1123      // "Mon, 02 Jan 2006 15:04:05 MST"
time.DateOnly     // "2006-01-02"
time.TimeOnly     // "15:04:05"
time.DateTime     // "2006-01-02 15:04:05"
t.Format(time.RFC3339)
t.Format(time.DateTime)

한국 포맷 예시

t.Format("2006년 1월 2일 15시 04분")
// "2026년 5월 24일 13시 00분"

한글이 섞여도 문제없다. 자리 표시자 부분만 정확하면 된다.


28.4 시간 연산

time.Duration

“얼마만큼의 시간 길이” 를 나타내는 타입이다. 나노초 단위의 정수다.

time.Second           // 1초
3 * time.Second       // 3초
500 * time.Millisecond // 0.5초
time.Hour + 30*time.Minute // 1시간 30분

Duration 값을 그대로 출력하면 사람 친화적으로 나온다.

fmt.Println(2 * time.Hour) // "2h0m0s"

Add / Sub

t := time.Now()
later := t.Add(2 * time.Hour) // 2시간 뒤
diff := later.Sub(t)          // Duration
  • Add(d)Time 을 반환
  • Sub(other)Duration 을 반환

Before / After / Equal

t1.Before(t2) // t1 이 t2 보다 이전인가
t1.After(t2)  // t1 이 t2 보다 이후인가
t1.Equal(t2)  // 같은 순간인가

== 가 아니라 Equal 을 쓰는 게 안전하다. 시간대가 다른 같은 순간을 비교할 때 == 는 false 가 나올 수 있지만 Equal 은 true 를 돌려준다.

Since / Until

자주 쓰는 짧은 도우미다.

start := time.Now()
doWork()
elapsed := time.Since(start)
// Now() - start 와 같다

deadline := someTime
remaining := time.Until(deadline)
// deadline - Now() 와 같다

Since 는 “그 뒤로 얼마나 지났나”, Until 은 “그때까지 얼마나 남았나”.


28.5 타이머와 Ticker

time.Sleep

가장 단순한 도구. 지정한 시간 동안 현재 고루틴을 멈춘다.

time.Sleep(2 * time.Second)

time.After

지정 시간 뒤에 값을 넣어 주는 채널을 반환한다. select 에서 타임아웃을 만들 때 자주 쓴다.

select {
case msg := <-ch:
    fmt.Println("받음:", msg)
case <-time.After(3 * time.Second):
    fmt.Println("3초 안에 못 받음")
}

22장의 select 와 자연스럽게 결합한다.

time.NewTimer

한 번만 울리는 타이머가 필요하면 NewTimer 를 쓴다. 중간에 멈추거나 재설정할 수 있다는 점이 After 와 다르다.

t := time.NewTimer(5 * time.Second)
defer t.Stop()

select {
case <-t.C:
    fmt.Println("5초 경과")
case <-cancel:
    // 취소된 경우, Stop 으로 깔끔히 정리
}

time.NewTicker

주기적으로 신호를 보내는 도구. 1초마다, 10초마다 등의 정기 작업에 쓴다.

ticker := time.NewTicker(1 * time.Second)
defer ticker.Stop()

for i := 0; i < 3; i++ {
    t := <-ticker.C
    fmt.Println("틱:", t)
}

다 쓴 Ticker 는 반드시 Stop() 한다. 안 그러면 내부 고루틴이 살아 있어 누수 원인이 된다.

9부 동시성과의 조합

타이머/Ticker 는 채널을 반환한다. 그래서 22~25장에서 본 도구들과 자연스럽게 결합한다.

ctx, cancel := context.WithTimeout(
    context.Background(),
    5 * time.Second,
)
defer cancel()

select {
case result := <-doWork():
    handle(result)
case <-ctx.Done():
    fmt.Println("타임아웃 또는 취소")
}

context.WithTimeout 내부도 사실은 타이머다.


28.6 시간대 (timezone)

time.Location 은 시간대를 표현한다.

표준 위치

의미
time.UTCUTC
time.Local현재 시스템의 로컬 시간대

임의 시간대 불러오기

loc, err := time.LoadLocation("Asia/Seoul")
if err != nil {
    return err
}
t := time.Now().In(loc)
fmt.Println(t)

위치 이름은 IANA 데이터베이스 기준이다.

  • "UTC"
  • "Asia/Seoul"
  • "America/New_York"
  • "Europe/London"

In(loc) 은 같은 순간을 다른 시간대로 표시한다. 순간 자체는 안 바뀌고, 시간대만 바뀐다.

흔한 함정

같은 순간이라도 Format 결과는 시간대에 따라 다르다.

t := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
fmt.Println(t.Format(time.DateTime))
// "2026-01-01 00:00:00"

kst, _ := time.LoadLocation("Asia/Seoul")
fmt.Println(t.In(kst).Format(time.DateTime))
// "2026-01-01 09:00:00"

서버 로그가 UTC 인데 사용자 화면은 KST 로 보여 줘야 한다면 저장은 UTC, 표시할 때 In(loc) 으로 변환하는 게 무난한 관례다.


28.7 정리

  • time.Time 은 시각 + 시간대를 함께 들고 다닌다
  • time.Now() 로 현재 시각, time.Date(...) 로 임의 시각
  • 포맷팅/파싱은 reference time 2006-01-02 15:04:05 를 외운다
    • time.RFC3339, time.DateTime 같은 상수도 활용
  • time.Duration 으로 길이를 표현
    • Add, Sub, Since, Until 로 연산
    • Before, After, Equal 로 비교
  • 타이머와 Ticker
    • time.Sleep 은 단순 대기
    • time.After 는 select 타임아웃
    • time.NewTicker 는 주기 작업
  • 시간대는 time.UTC, time.Local, 또는 time.LoadLocation("Asia/Seoul")

시간은 단순해 보이지만 함정이 많다. “순간 자체” 와 “표시 방식” 을 머리 속에서 분리해 두면 헷갈릴 일이 줄어든다.

다음 장에서는 파일 입출력을 다룬다. 시간만큼이나 자주 쓰는 영역이다.